#include "ExampleScene.h"

#include <graphics/utils.h>
#include <graphics/image2d.h>
#include <assert.h>
#include <math.h>
#include <render/renderer.h>

ExampleScene::ExampleScene()
    : Yam3DScene()
    , m_camera(0)
    , m_meshes()
    , m_passes()
    , m_perVertexProgram(0)
    , m_perFragmentProgram(0)
    , m_baseMap(0)
    , m_normalMap(0)
    , m_bumpMap(0)
{
    { // Bump map shader
        // Create cool vertex and fragment shaders.
        QGLShader *vshader = new QGLShader(QGLShader::Vertex);
        char vShaderStr[] =
            "uniform mat4 matModelViewProj;                                               \n"
            "uniform mat4 matModelView;                                                   \n"
            "uniform mat4 matModelViewInverseTranspose;                                   \n"
            "uniform vec3 vecEyePositionWorld;                                            \n"
            "uniform vec3 vecLightPositionWorld;                                          \n"
            "attribute vec4 a_position;                                                   \n"
            "attribute vec3 a_normal;                                                     \n"
            "attribute vec2 a_texCoord;                                                   \n"
            "attribute vec4 a_tangent;                                                    \n"
            "varying vec3 vViewVec;                                                       \n"
            "varying vec3 vLightVec;                                                      \n"
            "varying vec2 vTexCoord;                                                      \n"
            "void main()                                                                  \n"
            "{                                                                            \n"
            "   vec4 position =  matModelView * a_position;                               \n"
            "   vec3 normal = normalize((matModelViewInverseTranspose * vec4(a_normal, 0.0)).xyz);       \n"
            "   vec3 tangent = normalize((matModelViewInverseTranspose * a_tangent).xyz);     \n"
            "   vec3 bitangent =  normalize(a_tangent.w*cross(normal,tangent));           \n"
            "                                                                             \n"
            "   // Compute world-space direction vector                                   \n"
            "   vec3 viewDirectionWorld = vecEyePositionWorld - position.xyz;             \n"
            "   // Compute world-space light direction vector                             \n"
            "   vec3 lightDirectionWorld = vecLightPositionWorld - position.xyz;          \n"
            "                                                                             \n"
            "   // Create the tangent matrix                                              \n"
            "   mat3 tangentMat = mat3( tangent, bitangent, normal );                     \n"
            "   // Transform the view and light vectors into tangent space                \n"
            "   vViewVec = viewDirectionWorld * tangentMat;                               \n"
            "   vLightVec = lightDirectionWorld * tangentMat;                             \n"
            "                                                                             \n"
            "   // set position and pass texture coordinate to fragment shader            \n"
            "   gl_Position =  matModelViewProj * a_position;                             \n"
            "   vTexCoord    = a_texCoord;                                                \n"
            "}                                                                            \n";
        vshader->compileSourceCode(vShaderStr);

        QGLShader *fshader = new QGLShader(QGLShader::Fragment);
        char fShaderStr[] =
            "varying vec3 vViewVec;                                                       \n"
            "varying vec3 vLightVec;                                                      \n"
            "varying vec2 vTexCoord;                                                      \n"
            "uniform sampler2D sBaseMap;                                                  \n"
            "uniform sampler2D sBumpMap;                                                  \n"
            "void main()                                                                  \n"
            "{                                                                            \n"
            "   // Fetch basemap color                                                    \n"
            "   vec4 baseColor = texture2D( sBaseMap, vTexCoord );                        \n"
            "   // Fetch the tangent space normal from normal map                         \n"
            "   vec3 normal = texture2D( sBumpMap, vTexCoord ).xyz;                       \n"
            "   // Scale and bias from [0, 1] to [-1, 1] and normalize                    \n"
            "   normal = normalize( normal * 2.0 - 1.0 );                                 \n"
            "   // Normalize the light direction and view direction                       \n"
            "   vec3 lightDirection = normalize( vLightVec );                             \n"
            "   vec3 viewDirection = normalize( vViewVec );                               \n"
            "   // Compute N.L                                                            \n"
            "   float nDotL = dot( normal, lightDirection );                              \n"
            "   // Compute reflection vector                                              \n"
            "   vec3 reflection = ( 2.0 * normal * nDotL ) - lightDirection;              \n"
            "   // Compute R.V                                                            \n"
            "   float rDotV = max( 0.0, dot( reflection, viewDirection ) );               \n"
            "   // Compute Ambient term                                                   \n"
            "   vec4 ambient = vec4(0.368,0.368,0.368,1.0) * baseColor;                   \n"
            "   // Compute Diffuse term                                                   \n"
            "   vec4 diffuse = vec4(0.885,0.885,0.885,1.0) * nDotL * baseColor;           \n"
            "   // Compute Specular term                                                  \n"
            "   vec4 specular = vec4(0.488,0.488,0.488,1.0) * pow( rDotV, 25.0 );         \n"
            "   // Output final color                                                     \n"
            "   gl_FragColor = ambient + diffuse + specular;                              \n"
            "}                                                                            \n";
        fshader->compileSourceCode(fShaderStr);


        assert( m_perFragmentProgram == 0 );
        m_perFragmentProgram = new QGLShaderProgram();
        m_perFragmentProgram->addShader(vshader);
        m_perFragmentProgram->addShader(fshader);
        m_perFragmentProgram->link();
        m_perFragmentProgram->bind();
    }



    { // Per vertex shader
        // Create cool vertex and fragment shaders.
        QGLShader *vshader = new QGLShader(QGLShader::Vertex);
        char vShaderStr[] =
            "uniform mat4 matModelViewProj;                                               \n"
            "uniform mat4 matModelView;                                                   \n"
            "uniform mat4 matModelViewInverseTranspose;                                   \n"
            "uniform vec3 vecEyePositionWorld;                                            \n"
            "uniform vec3 vecLightPositionWorld;                                          \n"
            "attribute vec4 a_position;                                                   \n"
            "attribute vec3 a_normal;                                                     \n"
            "attribute vec2 a_texCoord;                                                   \n"
            "varying vec3 vNormal;                                                        \n"
            "varying vec3 vViewVec;                                                       \n"
            "varying vec3 vLightVec;                                                      \n"
            "varying vec2 vTexCoord;                                                      \n"
            "void main()                                                                  \n"
            "{                                                                            \n"
            "   gl_Position = matModelViewProj * a_position;                              \n"
            "   vNormal     = vec3(matModelViewInverseTranspose * vec4(a_normal, 0.0));   \n"
            "   vViewVec    = -vec3(matModelView * a_position);                           \n"
            "   vTexCoord   = a_texCoord;                                                 \n"
            "   vec4 position =  matModelView * a_position;                               \n"
            "   vec3 lightDirectionWorld = vecLightPositionWorld - position.xyz;          \n"
            "   vLightVec   = normalize( lightDirectionWorld );                           \n"
            "}                                                                            \n";
        vshader->compileSourceCode(vShaderStr);

        QGLShader *fshader = new QGLShader(QGLShader::Fragment);
        char fShaderStr[] =
            "varying vec3 vNormal;                                                       \n"
            "varying vec3 vViewVec;                                                      \n"
            "varying vec3 vLightVec;                                                      \n"
            "varying vec2 vTexCoord;                                                      \n"
            "uniform sampler2D sBaseMap;                                                  \n"
            "void main()                                                                  \n"
            "{                                                                            \n"
            "   // Fetch basemap color                                                    \n"
            "   vec4 baseColor = texture2D( sBaseMap, vTexCoord );                        \n"
            "   vec3 normal = normalize(vNormal);                                               \n"
            "   vec3 lightDirection = normalize( vLightVec );                             \n"
            "   vec3 viewDirection = normalize( vViewVec );                               \n"
            "   // Compute N.L                                                            \n"
            "   float nDotL = dot( normal, lightDirection );                              \n"
            "   // Compute reflection vector                                              \n"
            "   vec3 reflection = ( 2.0 * normal * nDotL ) - lightDirection;              \n"
            "   // Compute R.V                                                            \n"
            "   float rDotV = max( 0.0, dot( reflection, viewDirection ) );               \n"
            "   // Compute Ambient term                                                   \n"
            "   vec4 ambient = vec4(0.368,0.368,0.368,1.0) * baseColor;                   \n"
            "   // Compute Diffuse term                                                   \n"
            "   vec4 diffuse = vec4(0.885,0.885,0.885,1.0) * nDotL * baseColor;           \n"
            "   // Compute Specular term                                                  \n"
            "   vec4 specular = vec4(0.488,0.488,0.488,1.0) * pow( rDotV, 25.0 );         \n"
            "   // Output final color                                                     \n"
            "   gl_FragColor = ambient + diffuse + specular;                              \n"
            "}                                                                            \n";
        fshader->compileSourceCode(fShaderStr);


        assert( m_perVertexProgram == 0 );
        m_perVertexProgram = new QGLShaderProgram();
        m_perVertexProgram->addShader(vshader);
        m_perVertexProgram->addShader(fshader);
        m_perVertexProgram->link();
        m_perVertexProgram->bind();
    }


    // Create camera for our scene.
    assert( m_camera == 0 );
    m_camera = new graphics::Camera();
    m_camera->setTranslation( QVector3D(0,0,4.0f) );

    graphics::ShaderUniforms* uniforms = new graphics::ShaderUniforms();
    uniforms->setUniformValue("vecLightPositionWorld", m_camera->getTranslation()+QVector3D(-10,10,30) );

    // Create 6 meshes and set them to corect places
    m_meshes.resize(6);
    m_passes.resize(3);
    for( int i=0;  i<3; ++i )
    {
        m_passes[i] = new graphics::Pass();
        m_passes[i]->setCamera( m_camera );
        m_passes[i]->setShaderUniforms( uniforms );
        QVector3D delta(2.0,0,0);

        m_meshes[i] = graphics::genSphere( 16, 0.9f );
        m_meshes[i+3] = graphics::genPlane( 2, 2, 1.8f, 1.8f );
        m_meshes[i]->setTranslation( -delta + i*delta + QVector3D(0,1.0f,0) );
        m_meshes[i+3]->setTranslation( -delta + i*delta + QVector3D(0,-1.0f,0) );
    }

    // Create base and normal maps (RGB)
    graphics::Image2D baseMapImg(":/assets/fieldstone.png",false);
    m_baseMap = new render::Texture2D(&baseMapImg);
    graphics::Image2D normalMapImg(":/assets/fieldstone_dot3.png",false);
    m_normalMap = new render::Texture2D(&normalMapImg);
    graphics::Image2D bumpMapImg(":/assets/fieldstone_bump_dot3.png",false);
    m_bumpMap = new render::Texture2D(&bumpMapImg);

    // Pass 0 = per vertex lighting
    m_passes[0]->setShaderProgram(m_perVertexProgram);
    m_passes[0]->setTexture( 0, "sBaseMap", m_baseMap );
    m_passes[0]->getSamplerState(0)->setFiltering( graphics::SamplerState::LINEAR_MIPMAP_LINEAR, graphics::SamplerState::LINEAR );

    // Pass 1 = per pixel lighting, with normal map
    m_passes[1]->setShaderProgram( m_perFragmentProgram );
    m_passes[1]->setTexture( 0, "sBaseMap", m_baseMap );
    m_passes[1]->getSamplerState(0)->setFiltering( graphics::SamplerState::LINEAR_MIPMAP_LINEAR, graphics::SamplerState::LINEAR );
    m_passes[1]->setTexture( 1, "sBumpMap", m_normalMap );
    m_passes[1]->getSamplerState(1)->setFiltering( graphics::SamplerState::LINEAR_MIPMAP_LINEAR, graphics::SamplerState::LINEAR );

    // Pass 1 = per pixel lighting, with bump map
    m_passes[2]->setShaderProgram( m_perFragmentProgram );
    m_passes[2]->setTexture( 0, "sBaseMap", m_baseMap );
    m_passes[2]->getSamplerState(0)->setFiltering( graphics::SamplerState::LINEAR_MIPMAP_LINEAR, graphics::SamplerState::LINEAR );
    m_passes[2]->setTexture( 1, "sBumpMap", m_bumpMap );
    m_passes[2]->getSamplerState(1)->setFiltering( graphics::SamplerState::LINEAR_MIPMAP_LINEAR, graphics::SamplerState::LINEAR );
}

ExampleScene::~ExampleScene()
{
}

void ExampleScene::resize(int width, int height)
{
    m_camera->setPerspective(60.0f, float(width)/float(height), 2.0f, 10.0f);
    glViewport( 0,0, width, height);
}

void ExampleScene::update(float deltaTime)
{
    m_meshes[0]->setOrientation( m_meshes[0]->getOrientation()
        * (QQuaternion::fromAxisAndAngle(1,0,0,15*deltaTime)
        * QQuaternion::fromAxisAndAngle(0,1,0,16*deltaTime)
        * QQuaternion::fromAxisAndAngle(0,0,1,17*deltaTime)) );

    for(int i=1; i<m_meshes.size(); ++i )
    {
        m_meshes[i]->setOrientation(m_meshes[0]->getOrientation());
    }
}

void ExampleScene::render() const
{
    render::Renderer renderer;

    for(int i=0; i<m_passes.size(); ++i )
    {
        renderer.applyPass(m_passes[i]);

        renderer.render(m_meshes[i]);
        renderer.render(m_meshes[i+3]);
    }
}


